Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

30장. Kotlin 코드 테스트하기

코드를 짜고 나면
“이게 정말 잘 동작할까?“가 궁금해집니다.

매번 프로그램을 실행해 손으로 확인하는 것은
느리고 실수도 잦습니다.

그래서 우리는 테스트 코드를 씁니다.
코드가 코드를 검사하게 만드는 것입니다.

이 장에서는 코틀린에서
테스트를 작성하는 기본기를 배웁니다.


30.1 테스트가 필요한 이유

테스트 코드가 있으면 좋은 점은 많습니다.

  • 코드가 의도대로 동작하는지 자동으로 확인
  • 나중에 코드를 고쳐도 망가지지 않았는지 검증
  • “이 함수는 이렇게 동작한다“는 살아 있는 설명서

특히 마지막이 중요합니다.

좋은 테스트는
그 자체로 사용 설명서가 됩니다.

테스트를 읽으면
“이 코드를 어떻게 쓰는지“가 보입니다.


30.2 JUnit 5

코틀린 백엔드에서 가장 많이 쓰는
테스트 도구는 JUnit 5입니다.

29장에서 배운 대로
build.gradle.kts에 의존성을 추가합니다.

dependencies {
    testImplementation(kotlin("test"))
}

이제 src/test/kotlin 폴더에
테스트 코드를 작성하면 됩니다.


30.3 테스트 코드 작성하기

간단한 계산기를 테스트해 봅시다.

먼저 검사할 코드입니다.

class Calculator {
    fun add(a: Int, b: Int): Int = a + b
}

이제 테스트입니다.

import kotlin.test.Test
import kotlin.test.assertEquals

class CalculatorTest {

    @Test
    fun add_두_수를_더한다() {
        val calculator = Calculator()

        val result = calculator.add(2, 3)

        assertEquals(5, result)
    }
}

@Test는 “이 함수는 테스트다“라는 표식입니다. (27장 애너테이션)
assertEquals는 “두 값이 같은지 확인“합니다.

기대한 값과 실제 값이 다르면
테스트가 실패로 표시됩니다.


30.4 Given-When-Then

좋은 테스트에는 흐름이 있습니다.
많이 쓰는 방식이 Given-When-Then입니다.

  • Given : 준비 (필요한 값과 객체를 만든다)
  • When : 실행 (검사할 동작을 실행한다)
  • Then : 검증 (결과가 기대와 같은지 확인한다)
@Test
fun 잔액에서_출금하면_잔액이_줄어든다() {
    // Given
    val account = Account(balance = 1000)

    // When
    account.withdraw(300)

    // Then
    assertEquals(700, account.balance)
}

이 세 단계로 나누면
테스트가 읽기 쉬워집니다.


30.5 Kotlin의 백틱 함수 이름

위 예제에서 함수 이름이 특이했습니다.

fun 잔액에서_출금하면_잔액이_줄어든다() { ... }

코틀린에서는 함수 이름을
백틱(`)으로 감싸면
공백이나 한글도 자유롭게 쓸 수 있습니다.

@Test
fun `잔액에서 출금하면 잔액이 줄어든다`() {
    // ...
}

테스트 이름은 곧 설명이므로,
이렇게 문장으로 적으면
무엇을 검사하는지 한눈에 보입니다.

이 문법은 테스트 이름에 특히 잘 어울립니다.
일반 코드에서는 잘 쓰지 않습니다.


30.6 Assertion

검증에 쓰는 함수를
어서션(Assertion)이라고 합니다.

자주 쓰는 것들을 정리하면 이렇습니다.

함수확인 내용
assertEquals(a, b)두 값이 같은가
assertTrue(x)x가 참인가
assertFalse(x)x가 거짓인가
assertNull(x)x가 null인가
assertNotNull(x)x가 null이 아닌가
assertTrue(user.isActive)
assertNotNull(user.email)

30.7 Test Fixture

여러 테스트가
같은 준비 과정을 반복할 때가 있습니다.

이 공통 준비를
@BeforeEach로 묶을 수 있습니다.

class CalculatorTest {
    private lateinit var calculator: Calculator

    @BeforeEach
    fun setUp() {
        calculator = Calculator()   // 각 테스트 전에 실행
    }

    @Test
    fun `더하기`() {
        assertEquals(5, calculator.add(2, 3))
    }
}

@BeforeEach
각 테스트가 실행되기 직전에 매번 호출됩니다.
덕분에 모든 테스트가
깨끗한 상태에서 시작합니다.
(lateinit은 5장에서 배웠습니다.)


30.8 예외 테스트

“이 상황에서는 예외가 나야 한다“도
테스트할 수 있습니다.

assertThrows를 씁니다.

@Test
fun `음수 나이는 예외가 발생한다`() {
    assertThrows<IllegalArgumentException> {
        checkAge(-1)
    }
}

블록 안에서
지정한 예외가 발생하면 테스트 성공,
발생하지 않으면 실패입니다.

정상 동작뿐 아니라
“실패해야 하는 상황“도 검증하는 것이 중요합니다.


30.9 Parameterized Test

같은 로직을
여러 입력값으로 반복 검사하고 싶을 때가 있습니다.

이때 파라미터화 테스트(Parameterized Test)를 씁니다.

@ParameterizedTest
@ValueSource(ints = [2, 4, 6, 100])
fun `짝수인지 확인한다`(number: Int) {
    assertTrue(number % 2 == 0)
}

[2, 4, 6, 100] 각각에 대해
테스트가 한 번씩 실행됩니다.

비슷한 테스트를 여러 번 복사하는 대신
하나로 깔끔하게 묶을 수 있습니다.


30장을 마치며

이 장에서 우리는 다음을 배웠습니다.

  • 테스트가 왜 필요한지
  • JUnit 5로 테스트를 작성하는 기본기
  • Given-When-Then 흐름
  • 백틱으로 읽기 좋은 테스트 이름 짓기
  • 다양한 Assertion과 예외 테스트
  • @BeforeEach와 파라미터화 테스트

지금까지는 의존성이 없는 코드를 테스트했습니다.
다음 장에서는 다른 객체에 의존하는 코드를
어떻게 테스트하는지 배웁니다.